package com.rex.saas.service;

import com.rex.saas.constants.RedisConstants;
import com.alibaba.fastjson.JSON;
import com.auth0.jwk.InvalidPublicKeyException;
import com.auth0.jwk.Jwk;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.lang.JoseException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.security.PublicKey;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class AppleIdValidService {
    @Resource(name = "stringRedisTemplate")
    private RedisTemplate<String, String> redisTemplate;

    public boolean isValid(String accessToken) {
        CusJws cusJws = this.getJws(accessToken);
        if (cusJws == null) {
            log.error("accessToken格式非法（非Jws格式）！accessToken={}", accessToken);
            return false;
        }
        // exp
        long curTime = System.currentTimeMillis();
        if (cusJws.getJwsPayload().getExp() * 1000 < curTime) {
            log.error("accessToken已过期！accessToken={}", accessToken);
            return false;
        }
        // iss
        if (!JwsPayload.ISS.equals(cusJws.getJwsPayload().getIss())) {
            log.error("accessToken签发来源不合法！iss={}", cusJws.getJwsPayload().getIss());
            return false;
        }
        // 校验签名
        if (!this.verifySignature(accessToken, cusJws.getJwsHeader().kid)) {
            log.error("accessToken签名验证失败！accessToken={}", accessToken);
            return false;
        }
        log.info("accessToken,验证通过！accessToken={}", accessToken);
        return true;
    }

    /**
     * verify signature
     */
    private boolean verifySignature(String accessToken, String kid) {
        PublicKey publicKey = this.getAppleIdPublicKey(kid);
        JsonWebSignature jsonWebSignature = new JsonWebSignature();
        jsonWebSignature.setKey(publicKey);
        try {
            jsonWebSignature.setCompactSerialization(accessToken);
            return jsonWebSignature.verifySignature();
        } catch (JoseException e) {
            log.error("签名验证异常！", e);
            return false;
        }
    }

    /**
     * publicKey会本地缓存1天，减少请求Apple Server的次数
     */
    private PublicKey getAppleIdPublicKey(String kid) {
        String publicKeyStr = redisTemplate.opsForValue().get(RedisConstants.REDIS_KEY_APPLE_ID_PUBLIC_KEY);
        if (publicKeyStr == null) {
            publicKeyStr = this.getAppleIdPublicKeyFromRemote();
            if (publicKeyStr == null) {
                return null;
            }
            try {
                PublicKey publicKey = this.publicKeyAdapter(publicKeyStr, kid);
                redisTemplate.opsForValue().set(RedisConstants.REDIS_KEY_APPLE_ID_PUBLIC_KEY, publicKeyStr, 24, TimeUnit.HOURS);
                return publicKey;
            } catch (Exception ex) {
                log.error("获取AppleId公钥异常！", ex);
                return null;
            }
        }
        return this.publicKeyAdapter(publicKeyStr, kid);
    }

    /**
     * 将appleServer返回的publicKey转换成PublicKey对象
     */
    private PublicKey publicKeyAdapter(String publicKeyStr, String kid) {
        if (!StringUtils.hasText(publicKeyStr)) {
            return null;
        }
        Map<String, Object> maps = (Map<String, Object>) JSON.parse(publicKeyStr);
        List<Map<String, Object>> keys = (List<Map<String, Object>>) maps.get("keys");
        Map<String, Object> o = new HashMap<>();
        for (Map<String, Object> key : keys) {
            if (kid.equals(key.get("kid"))) {
                o = key;
                break;
            }
        }
        Jwk jwa = Jwk.fromValues(o);
        try {
            return jwa.getPublicKey();
        } catch (InvalidPublicKeyException e) {
            log.error("PublicKey转换异常！", e);
            return null;
        }
    }

    /**
     * 从apple Server获取publicKey
     */
    private String getAppleIdPublicKeyFromRemote() {
        ResponseEntity<String> responseEntity = new RestTemplate().getForEntity("https://appleid.apple.com/auth/keys", String.class);
        if (responseEntity.getStatusCode() != HttpStatus.OK) {
            return null;
        }
        return responseEntity.getBody();
    }

    private CusJws getJws(String identityToken) {
        String[] arrToken = identityToken.split("\\.");
        if (arrToken.length != 3) {
            return null;
        }
        Base64.Decoder decoder = Base64.getDecoder();
        JwsHeader jwsHeader = JSON.parseObject(new String(decoder.decode(arrToken[0])), JwsHeader.class);
        JwsPayload jwsPayload = JSON.parseObject(new String(decoder.decode(arrToken[1])), JwsPayload.class);
        return new CusJws(jwsHeader, jwsPayload, arrToken[2]);
    }

    @Data
    @AllArgsConstructor
    private static class CusJws {
        private JwsHeader jwsHeader;
        private JwsPayload jwsPayload;
        private String signature;
    }

    @Data
    private static class JwsHeader {
        private String kid;
        private String alg;
    }

    @Data
    private static class JwsPayload {
        private final static String ISS = "https://appleid.apple.com";
        private String iss;
        private String sub;
        private String aud;
        private long exp;
        private long iat;
        private String nonce;
        private String email;
        private boolean email_verified;
    }
}
